package org.pentaho.gwtutil;

import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.TreeSet;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

/**
 * This class is a utility that generates Java code from simple GWT based XML markup.
 * 
 * @author Will Gorman (wgorman@pentaho.com)
 */
public class GWTUIGen {

  static int currId = 0;
  
  public static void main(String args[]) throws Exception {
    if (args.length == 1) {
      File f = new File(args[0]);
      generateFile(f);
    } else {
      // find files in current directory
      File f = new File(".");
      List files = new ArrayList();
      files.add(f);
      while (files.size() > 0) {
        File currfile = (File)files.remove(0);
        for (File file : currfile.listFiles()) {
          if (file.getName().endsWith(".gwtui.xml")) {
            System.out.println("Generating from file " + file);
            generateFile(file);
          } else if (file.isDirectory()) {
            files.add(file);     
          }
        }
      }
      renderI18N(f);
    }
  }

  
  
  public static String generateI18nName(String text) {
    String str[] = text.toLowerCase().split("\\s+");
    String camelCase = "";
    for (int i = 0; i < str.length; i++) {
      if (i == 0) {
        camelCase += str[i];
      } else {
        camelCase += str[i].substring(0,1).toUpperCase() + str[i].substring(1);
      }
    }
    return camelCase;
  }
  
  static class I18N {
    String i18nInterface;
    String i18nSingleton;
    String i18nCall;
    String i18nPrefix;
    int count = 0;
    
    TreeSet<String> i18nProps = new TreeSet<String>();
    HashMap<String, String> i18nPropLookup = new HashMap<String, String>();
  }
  
  static String getFourCharCount(int count) {
    if (count < 10) return "000" + count;
    if (count < 100) return "00" + count;
    if (count < 1000) return "0" + count;
    return "" + count;
  }
  
  static String getI18NText(String text) {
    
    if (i18n != null && text.trim().length() > 0) {
      
      String textLabel = text.replaceAll("[^a-zA-Z\\s]", "");
      // if label contains special chars, use counter to reference
      if (!textLabel.equals(text)) {
        String tmpName = generateI18nName(i18n.i18nPrefix + " " + textLabel);
        if (textLabel.trim().length() == 0 || ( 
            i18n.i18nProps.contains(tmpName) && 
            !i18n.i18nPropLookup.get(tmpName).equals(text))) {
          textLabel += "_" + getFourCharCount(i18n.count++);
        }
      }
      String i18nName = generateI18nName(i18n.i18nPrefix + " " + textLabel);
      if (!i18n.i18nProps.contains(i18nName)) {
        i18n.i18nProps.add(i18nName);
        i18n.i18nPropLookup.put(i18nName, text);
      }
      return i18n.i18nCall + "." + i18nName + "()";
    } else {
      return "\"" + text + "\"";
    }
  }
  
  static void renderI18N(File root) {
    if (i18n == null) return;
    String filename = i18n.i18nInterface.replaceAll("\\.", "/");
    System.out.println("Generating I18N file " + filename + ".java and " + filename + ".properties");
    String packageName = i18n.i18nInterface.substring(0, i18n.i18nInterface.lastIndexOf("."));
    String className = i18n.i18nInterface.substring(i18n.i18nInterface.lastIndexOf(".")  + 1);
    try {
      PrintWriter pw = new PrintWriter(new FileWriter(filename + ".java"));
      PrintWriter ppw = new PrintWriter(new FileWriter(filename + ".properties"));
      pw.println("package " + packageName + ";");
      pw.println("");
      pw.println("import com.google.gwt.i18n.client.Constants;");
      pw.println("");
      pw.println("public interface " + className + " extends Constants {");

      for (String prop : i18n.i18nProps) {
        pw.println("   public String " + prop + "();");
        ppw.println(prop + "=" + i18n.i18nPropLookup.get(prop));
      }
      
      pw.println("}");
      pw.close();
      ppw.close();
      
    } catch (Exception e) {
      e.printStackTrace();
    }
    
  }

  static I18N i18n = null; //new I18N();
  
  public static void generateFile(File f) throws Exception {
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    DocumentBuilder builder = factory.newDocumentBuilder();
    Document document = builder.parse( f );
    Element root = document.getDocumentElement();
    String className = root.getAttribute("className");
    if (i18n == null) {
      System.out.println("Initing I18N");
      i18n = new I18N();
      i18n.i18nInterface = root.getAttribute("i18nInterface");
      i18n.i18nSingleton = root.getAttribute("i18nSingleton");
      i18n.i18nCall = i18n.i18nSingleton.substring(i18n.i18nSingleton.lastIndexOf(".") + 1) + "." + root.getAttribute("i18nCall"); // "Messages.getUIInstance()";
    }
    i18n.i18nPrefix = f.getName().substring(0, f.getName().length() - 10) + "_";
    i18n.count = 0;

    String packageName = null;
    if (className.lastIndexOf(".") >= 0) {
      packageName = className.substring(0, className.lastIndexOf("."));
      className = className.substring(className.lastIndexOf(".") + 1);
    }
    File javaFile = new File(f.getParentFile(), className + ".java");
    if (javaFile.exists()) {
      // backup(javaFile);
    }
    
    PrintWriter pw = new PrintWriter(javaFile);
    if (packageName != null) {
     pw.println("package " + packageName + ";");
    }

    StringBuffer imports = new StringBuffer();
    StringBuffer members = new StringBuffer();
    StringBuffer initMethod = new StringBuffer();
    StringBuffer getMethods = new StringBuffer();
    
    if (root.getAttributeNode("imports") != null) {
      String importsAttrib[] = root.getAttribute("imports").split(",");
      for (int i = 0; i < importsAttrib.length; i++) {
        imports.append("import ").append(importsAttrib[i]).append(";\n"); 
      }
    }
    
    NodeList list = root.getChildNodes();
    for (int i = 0; i < list.getLength(); i++) {
      if (list.item(i) instanceof Element) {
        Element element = (Element)list.item(i);
        renderElement(element, imports, members, initMethod, getMethods);
      }
    }
    pw.println();
    pw.println("import com.google.gwt.user.client.ui.*;");
    if (i18n != null) {
      pw.println("import " + i18n.i18nSingleton +";");
    }
    pw.println(imports);
    pw.println();
    pw.println("public class " + className + " {");
    pw.println();
    pw.println(members);
    pw.println();
    pw.println("  public void init() {");
    pw.println(initMethod);
    pw.println("  }");
    pw.println();
    pw.println(getMethods);
    pw.println("}");
    pw.close();
  }

  static String genConstructor(String type, Element element, StringBuffer imports) {
    String text = getI18NText(element.getAttribute("text"));    
    if ((type.equals("Button") || type.equals("Label") || type.equals("CheckBox") || type.equals("TreeItem") ) 
        && element.getAttributeNode("text") != null) {
      return "new " + type + "(" + text + ")";
    } else if (type.equals("MenuBar") && element.getAttributeNode("vertical") != null) {
      return "new " + type + "(" + element.getAttribute("vertical") + ")";
    } else if (type.equals("MenuItem") && element.getAttributeNode("text") != null) {
      if (imports.indexOf(".Command;") == -1) {
        imports.append("import com.google.gwt.user.client.Command;\n");
      }
      return "new " + type + "(" + text + ", (Command)null)";
    } else if (type.equals("DisclosurePanel") && element.getAttributeNode("text") != null) {
      if (element.getAttributeNode("isOpen") != null) {
        return "new " + type + "(" + text + ", "+element.getAttribute("isOpen") +")";
      } else {
        return "new " + type + "(" + text + ")";
      }
    } else if (type.equals("RadioButton")) {
      return "new " + type + "(\"" + element.getAttribute("group") + "\", " + text + ")";
    } else {
      return "new " + type + "()";
    }
  }
  
  static String toCamel(String str) {
    return str.substring(0, 1).toUpperCase() + str.substring(1);
  }
  
  static String renderElement(Element element, StringBuffer imports, StringBuffer members, StringBuffer initMethod, StringBuffer getMethods) {
    String type = element.getNodeName();
    if (type.equals("Item")) return null;
    String id = element.getAttribute("id");
    if (id == null || id.length() == 0) {
      id = "obj" + currId++;
      initMethod.append("    " + type + " " + id + " = "+genConstructor(type, element, imports)+";\n");
    } else {
      members.append("  private " + type + " " + id + " = " + genConstructor(type, element, imports) + ";\n");
      getMethods.append("  public " + type + " get" + toCamel(id) + "() {\n    return " + id + ";\n  }\n");
    }

    if (element.getAttributeNode("horizontalAlignment") != null) {
      initMethod.append("    " + id + ".setHorizontalAlignment(" + type +"." + element.getAttribute("horizontalAlignment") + ");\n");
    }

    if (element.getAttributeNode("verticalAlignment") != null) {
      initMethod.append("    " + id + ".setVerticalAlignment(" + type +"." + element.getAttribute("verticalAlignment") + ");\n");
    }

    // traverse children
    NodeList list = element.getChildNodes();
    int nc = 0;
    for (int i = 0; i < list.getLength(); i++) {
      if (!(list.item(i) instanceof Element)) continue;
      Element child = (Element)list.item(i);
      String childId = renderElement(child, imports, members, initMethod, getMethods);
      // connect the child to the parent
      
      if (type.equals("TabPanel")) {
        String tabName = child.getAttribute("tabName");
        initMethod.append("    " + id + ".add(" + childId + ",\"" + tabName+"\");\n");
      } else if (type.equals("ListBox")) {
        String text = getI18NText(child.getAttribute("text"));

        
//        String text = "\"" + child.getAttribute("text") + "\"";
        initMethod.append("    " + id + ".addItem(" + text + ");\n");
      } else if (type.equals("DockPanel")) {
        String dock = child.getAttribute("dock");
        initMethod.append("    " + id + ".add(" + childId + ", DockPanel." + dock+");\n");
      } else if (type.equals("MenuBar") || type.equals("Tree") || type.equals("TreeItem")) {
        initMethod.append("    " + id + ".addItem(" + childId + ");\n");
        if (element.hasAttribute("itemCommand")) {
          if (imports.indexOf(".Command;") == -1) {
            imports.append("import com.google.gwt.user.client.Command;\n");
          }
          
          if (members.indexOf("ItemCommand {") == -1) {
            members.append("  public static interface ItemCommand {\n");
            members.append("    public void execute(String text);\n");
            members.append("  }");
            members.append("  public static interface CommandHelper {\n");
            members.append("    public Command getCommand(MenuItem item);\n");
            members.append("  }\n");
          }
          
          String itemCommand = element.getAttribute("itemCommand");
          if (members.indexOf(itemCommand) == -1) {
            members.append("  ItemCommand " + itemCommand + ";\n");
            members.append("  CommandHelper " + itemCommand + "Helper;\n");
            
            getMethods.append("  public void set" + toCamel(itemCommand) + "(ItemCommand itemCommand) {\n");
            getMethods.append("    this."+itemCommand + " = itemCommand;\n");
            getMethods.append("    " + itemCommand + "Helper = new CommandHelper() {\n");
            getMethods.append("      public Command getCommand(final MenuItem item) {\n");
            getMethods.append("        Command cmd = new Command() {\n");
            getMethods.append("          public void execute() {\n");
            getMethods.append("            "+itemCommand + ".execute(item.getText());\n");
            getMethods.append("          }\n");
            getMethods.append("        };\n");
            getMethods.append("        return cmd;\n");
            getMethods.append("      }\n");
            getMethods.append("    };\n");
            getMethods.append("  }\n");
          }
          initMethod.append("    " + childId + ".setCommand("+itemCommand+"Helper.getCommand(" + childId + "));\n");
        }
      } else if (type.equals("MenuItem")) {
        initMethod.append("    " + id + ".setSubMenu(" + childId + ");\n");
      } else if (type.endsWith("FlexTable") && child.hasAttribute("row") && child.hasAttribute("col")) {
        initMethod.append("    " + id + ".setWidget(" + child.getAttribute("row") + "," + child.getAttribute("col") + "," + childId +");\n");
        if (child.hasAttribute("colspan")) {
          initMethod.append("    " + id + ".getFlexCellFormatter().setColSpan(" + child.getAttribute("row") + "," + 
              child.getAttribute("col") + "," + child.getAttribute("colspan") + ");\n");
        }
        if (child.hasAttribute("rowspan")) {
          initMethod.append("    " + id + ".getFlexCellFormatter().setRowSpan(" + child.getAttribute("row") + "," + 
              child.getAttribute("col") + "," + child.getAttribute("rowspan") + ");\n");
        }
        if (child.hasAttribute("colWidth")) {
          initMethod.append("    " + id + ".getColumnFormatter().setWidth(" + child.getAttribute("col") + ", \"" + child.getAttribute("colWidth") + "\");\n");
        }
        if (child.hasAttribute("cellVerticalAlignment")) {
          initMethod.append("    " + id + ".getFlexCellFormatter().setVerticalAlignment(" + child.getAttribute("row") + "," + 
              child.getAttribute("col") + ",HasVerticalAlignment."+child.getAttribute("cellVerticalAlignment")+");\n");
        }
        if (child.hasAttribute("cellHorizontalAlignment")) {
          initMethod.append("    " + id + ".getFlexCellFormatter().setHorizontalAlignment(" + child.getAttribute("row") + "," + 
              child.getAttribute("col") + ",HasHorizontalAlignment."+child.getAttribute("cellHorizontalAlignment")+");\n");
        }
      } else if (type.equals("HorizontalSplitPanel")) {
        if (nc == 0) {
          initMethod.append("    " + id + ".setLeftWidget(" + childId + ");\n");
        } else {
          initMethod.append("    " + id + ".setRightWidget(" + childId + ");\n");
        }
      } else {
        initMethod.append("    " + id + ".add(" + childId + ");\n");
      }
      nc++;
    }
    // not sure if this is necessary
    if (type.equals("DeckPanel") && list.getLength() > 0) {
      initMethod.append("    " + id + ".showWidget(0);\n");
    }
    
    if (type.equals("TabPanel") && element.getAttributeNode("selected") != null) {
      initMethod.append("    " + id + ".selectTab(" + element.getAttribute("selected") + ");\n");
    }
    
    if (element.getAttributeNode("characterWidth") != null) {
      initMethod.append("    " + id + ".setCharacterWidth(" + element.getAttribute("characterWidth")+");\n");
    }
    if (element.getAttributeNode("width") != null) {
      initMethod.append("    " + id + ".setWidth(\"" + element.getAttribute("width")+"\");\n");
    }

    if (element.getAttributeNode("height") != null) {
      initMethod.append("    " + id + ".setHeight(\"" + element.getAttribute("height")+"\");\n");
    }

    if (element.getAttributeNode("alwaysShowScrollBars") != null) {
      initMethod.append("    " + id + ".setAlwaysShowScrollBars(" + element.getAttribute("alwaysShowScrollBars") + ");\n");
    }
    
    if (type.equals("Label") && element.getAttributeNode("wordWrap") != null) {
      initMethod.append("    " + id + ".setWordWrap(" + element.getAttribute("wordWrap")+");\n");
    }
    
    if (type.equals("TextArea") && element.getAttributeNode("lines") != null) {
      initMethod.append("    " + id + ".setVisibleLines(" + element.getAttribute("lines")+");\n");
    }
    return id;
  }
  
  static void backup(File f) {
    File nFile = new File(f.getName() + ".bak");
    int i = 0;
    while (nFile.exists()) {
      nFile = new File(f.getName()+ ".bak." + i++);
    }
    f.renameTo(nFile);
  }
}
